/**
 * Copyright (c) 2009 Teoti Graphix, LLC See:
 *     http://tkarchitecture.teotigraphix.org
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 * @author Michael Schmalle mschmalle@teotigraphix.com
 */
package org.teotigraphix.dx.patterns.manager.meta
{

import flash.display.DisplayObject;
import flash.utils.getQualifiedClassName;
import flash.utils.getQualifiedSuperclassName;

import mx.core.IUIComponent;
import mx.utils.DescribeTypeCache;
import mx.utils.DescribeTypeCacheRecord;

import org.teotigraphix.dx.core.ToolKitUtil;
import org.teotigraphix.dx.interfaces.IMetaManager;

[Mixin]

/**
 * @private
 * 
 * The default <code>IMetaManager</code> implementation for the toolkit.
 * 
 * <p><em>This class is internal to the toolkit.</em></p>
 * 
 * @author Michael Schmalle
 * @copyright Teoti Graphix, LLC
 * @date 12-08-09
 */
public class MetaManagerImpl
	   implements IMetaManager
{
	include '../../../core/Version.as'
	
	//--------------------------------------------------------------------------
	//
	//  Public Static Initialization
	//
	//--------------------------------------------------------------------------
	
	ToolKitUtil.registerSingleton(
		"org.teotigraphix.dx.interfaces::IMetaManager", 
		MetaManagerImpl);
	
	//--------------------------------------------------------------------------
	//
	//  Public Mixin :: Methods - ISystemManager
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	public static function init(root:DisplayObject):void
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * @private
	 */
	public function MetaManagerImpl()
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private Class :: Properties
	//
	//--------------------------------------------------------------------------
	
	/**
	 * The metadata identity map by className.
	 * @private
	 */
	private static var metaDataByClassName:Object = {};
	
	/**
	 * Save some memory and startup performance by not parsing non 
	 * framework metadata.
	 * @private
	 */
	private static var excludes:Array = [];	
	
	/**
	 * @private
	 */
	private static var instance:IMetaManager = null;
	
	//--------------------------------------------------------------------------
	//
	//  IToolKitMetaManager API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @copy org.teotigraphix.dx.interfaces.IMetaManager#resolveClassMetaData()
	 */
	public function resolveClassMetaData(instance:Object):ClassMetaData
	{
		var className:String = ("className" in instance) ?
			Object(instance).className :
			getQualifiedClassName(instance);
		
		if (instance is IUIComponent)
		{
			className = getQualifiedClassName(instance);
		}
		
		return parseClassMetaData(className);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public Class :: Methods
	//
	//--------------------------------------------------------------------------
	
    /**
     * @private
     */
    public static function getInstance():IMetaManager
    {
        if (!instance) 
        {
        	excludes["Object"] = true;
        	excludes["mx.core::UIComponent"] = true;
//			excludes["spark.components.supportClasses::SkinnableComponent"] = true;
//			excludes["spark.components.supportClasses::Skin"] = true;
//			excludes["spark.components::Group"] = true;
		
			instance = new MetaManagerImpl();
        }
        
        return instance;
    }

	//--------------------------------------------------------------------------
	//
	//  Private Class :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 * This method only runs once per className.
	 */
	private static function parseClassMetaData(className:String):ClassMetaData
	{
		// check to see if the className's metadata has been cached
		var cmd:ClassMetaData = metaDataByClassName[className];
		if (cmd)
			return cmd;
		
		cmd = new ClassMetaData();
		
		var type:Class = ToolKitUtil.getDefinition(className) as Class;
		
		// go into superclass if needed
		var superclassName:String = getQualifiedSuperclassName(type);
		if(superclassName != null && excludes[superclassName] == null)
		{
			var bcmd:ClassMetaData = parseClassMetaData(superclassName);

			cmd.componentAliases = bcmd.componentAliases.concat();
			cmd.skinPartAliases = bcmd.skinPartAliases.concat();
			cmd.modelAliases = bcmd.modelAliases.concat();
			cmd.componentHandlers = bcmd.componentHandlers.concat();
			cmd.modelHandlers = bcmd.modelHandlers.concat();
			cmd.skinHandlers = bcmd.skinHandlers.concat();
			cmd.skinPartHandlers = bcmd.skinPartHandlers.concat();
			cmd.modelBindings = bcmd.modelBindings.concat();
			cmd.styleBindings = bcmd.styleBindings.concat();
			
			// halo
			cmd.skinParts = bcmd.skinParts.concat();
			cmd.skinStates = bcmd.skinStates.concat();
		}
		
		var record:DescribeTypeCacheRecord = DescribeTypeCache.describeType(type);
		var reflection:XML = record.typeDescription;
		
		// resolve all declared metadata for class
		resolveComponentAlias(reflection, cmd.componentAliases);
		resolveSkinPartAlias(reflection, cmd.skinPartAliases);
		resolveModelAlias(reflection, cmd.modelAliases);
		resolveComponentHandler(reflection, cmd.componentHandlers);
		resolveModelHandler(reflection, cmd.modelHandlers); 
		resolveSkinHandler(reflection, cmd.skinHandlers);
		resolveSkinPartHandler(reflection, cmd.skinPartHandlers);
		resolveModelBinding(reflection, cmd.modelBindings);
		resolveStyleBinding(reflection, cmd.styleBindings);
		
		// halo
		resolveSkinPart(reflection, cmd.skinParts);
		resolveSkinState(reflection, cmd.skinStates);		
		
		// cache the metadata
		metaDataByClassName[className] = cmd;
		
		return cmd;
	}
	
	//----------------------------------
	//  [ServiceBinding]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveModelBinding(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnModelBinding(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:ModelBindingInfo = new ModelBindingInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [StyleBinding]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveStyleBinding(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnStyleBinding(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:StyleBindingInfo = new StyleBindingInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [SkinHandler]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveSkinHandler(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnSkinHandler(reflection);	
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:SkinHandlerInfo = new SkinHandlerInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [ComponentHandler]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveComponentHandler(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnComponentHandler(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:ComponentHandlerInfo = new ComponentHandlerInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [SkinState]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveSkinState(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnSkinState(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:SkinStateInfo = new SkinStateInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [ModelHandler]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveModelHandler(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnModelHandler(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:ModelHandlerInfo = new ModelHandlerInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [SkinPartHandler]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveSkinPartHandler(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnSkinPartHandler(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++) 
		{
			var info:SkinPartHandlerInfo = new SkinPartHandlerInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}	

	//----------------------------------
	//  [ComponentAlias]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveComponentAlias(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnComponentAlias(reflection);	
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:ComponentAliasInfo = new ComponentAliasInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [SkinPartAlias]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveSkinPartAlias(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnSkinPartAlias(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{		
			var info:SkinPartAliasInfo = new SkinPartAliasInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//----------------------------------
	//  [SkinPart]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveSkinPart(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnSkinPart(reflection);
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{		
			var info:SkinPartInfo = new SkinPartInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}	

	//----------------------------------
	//  [ModelAlias]
	//----------------------------------
	
	/**
	 * @private
	 */
	private static function resolveModelAlias(
		reflection:XML, infos:Array):void 
	{
		var metadata:XMLList = returnModelAlias(reflection);	
		var len:int = metadata.length();
		for (var i:int = 0; i < len; i++)
		{
			var info:ModelAliasInfo = new ModelAliasInfo(metadata[i]);
			if (!contains(infos, info))
				infos.push(info);
		}
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private Class Utility :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private static function contains(infos:Object, info:Object):Boolean
	{
		var len:int = infos.length;
		for (var i:int = 0; i < len; i++)
			if (infos[i].equals(info))
				return true;
		return false;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private Class Reflection :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private static function returnModelBinding(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.variable.metadata.
			(@name == ClassMetaDataType.MODEL_BINDING);
		metadata += reflection.factory.accessor.metadata.
			(@name == ClassMetaDataType.MODEL_BINDING);
		return metadata;
	}
	
	/**
	 * @private
	 */
	private static function returnStyleBinding(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.variable.metadata.
			(@name == ClassMetaDataType.STYLE_BINDING);				
		metadata += reflection.factory.accessor.metadata.
			(@name == ClassMetaDataType.STYLE_BINDING);
		return metadata;
	}
	
	/**
	 * @private
	 */
	private static function returnSkinHandler(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.metadata.
			(@name == ClassMetaDataType.SKIN_HANDLER);	
		return metadata;
	}
	
	/**
	 * @private
	 */
	private static function returnSkinPartHandler(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.accessor.metadata.
			(@name == ClassMetaDataType.SKIN_PART_HANDLER);
		metadata += reflection.factory.variable.metadata.
			(@name == ClassMetaDataType.SKIN_PART_HANDLER);
		metadata += reflection.factory.metadata.
			(@name == ClassMetaDataType.SKIN_PART_HANDLER);	
		return metadata;
	}
	
	/**
	 * @private
	 */
	private static function returnComponentHandler(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.metadata.
			(@name == ClassMetaDataType.COMPONENT_HANDLER);
		return metadata;
	}
	
	/**
	 * @private
	 */
	private static function returnSkinState(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.metadata.
			(@name == ClassMetaDataType.SKIN_STATE);
		return metadata;
	}	
	
	/**
	 * @private
	 */
	private static function returnModelHandler(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.metadata.
			(@name == ClassMetaDataType.MODEL_HANDLER);
		return metadata;
	}
		
	/**
	 * @private
	 */
	private static function returnComponentAlias(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.accessor.metadata.
			(@name == ClassMetaDataType.COMPONENT_ALIAS);
		metadata += reflection.factory.variable.metadata.
			(@name == ClassMetaDataType.COMPONENT_ALIAS);
		return metadata;
	}
	
	/**
	 * @private
	 */
	private static function returnSkinPartAlias(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.accessor.metadata.
			(@name == ClassMetaDataType.SKIN_PART_ALIAS);
		metadata += reflection.factory.variable.metadata.
			(@name == ClassMetaDataType.SKIN_PART_ALIAS);
		return metadata;
	}
	
	/**
	 * @private
	 */
	private static function returnSkinPart(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.accessor.metadata.
			(@name == ClassMetaDataType.SKIN_PART);
		metadata += reflection.factory.variable.metadata.
			(@name == ClassMetaDataType.SKIN_PART);
		return metadata;
	}

	/**
	 * @private
	 */
	private static function returnModelAlias(reflection:XML):XMLList
	{
		var metadata:XMLList = reflection.factory.accessor.metadata.
			(@name == ClassMetaDataType.MODEL_ALIAS);
		metadata += reflection.factory.variable.metadata.
			(@name == ClassMetaDataType.MODEL_ALIAS);
		return metadata;
	}	
}
}